﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Resources;
using System.Windows;
using System.Windows.Markup;

namespace RaisingForce.Raise.Presentation.Localization.Xaml
{
    /// <summary>
    /// Represents a localization makrup extension.
    /// </summary>
    [MarkupExtensionReturnType(typeof(object))]
    [ContentProperty("Key")]
    public class LocExtension : MarkupExtension
    {
        #region Constructors

        /// <summary>
        /// Initializes new instance of the class.
        /// </summary>
        public LocExtension() { }

        /// <summary>
        /// Initializes new instance of the class.
        /// </summary>
        /// <param name="key">The resource key.</param>
        public LocExtension(string key)
        {
            this.Key = key;
        }

        #endregion

        #region Private Declarations

        /// <summary>
        /// The property localized by the instance.
        /// </summary>
        private object targetProperty;

        /// <summary>
        /// The instance that owns the <see cref="DependencyProperty"/> localized by the instance.
        /// </summary>
        private WeakReference targetObject;

        /// <summary>
        /// The list of instances created by a template that own the <see cref="DependencyProperty"/>
        /// localized by the instance.
        /// </summary>
        private List<WeakReference> targetObjects;

        private object getLocalizedValue()
        {
            return GetLocalizedValue(this.Key, this.Format);
        }

        /// <summary>
        /// Returns the object that corresponds to the specified resource key.
        /// </summary>
        /// <param name="key">The resource key.</param>
        /// <returns>The object that corresponds to the specified resource key.</returns>
        private static object GetLocalizedValue(string key, string format)
        {
            if (string.IsNullOrEmpty(key))
            {
                return string.Empty;
            }
            ResourceManager manager = LocalizationManager.ResourceManager;
            object value;
#if DEBUG
            //value = manager == null ? string.Empty : manager.GetObject(key) ?? "[Resource: " + key + "]";
            if (manager == null)
            {
                value = string.Empty;
            }
            else
            {
                value = manager.GetObject(key);
                if (value == null)
                {
                    throw new ArgumentOutOfRangeException(
                        paramName: "key",
                        actualValue: key,
                        message: "Resource not found.");
                }
            }
#else
            value = manager == null ? string.Empty : manager.GetObject(key) ?? string.Empty;
#endif
            return string.IsNullOrEmpty(format) ?
                value :
                string.Format(format, value);
        }

        #endregion

        #region Internal Declarations

        /// <summary>
        /// Gets value indicating if the instance localized by this instance is alive.
        /// </summary>
        internal bool IsAlive
        {
            get
            {   // Verify if the extension is used in a template
                if (this.targetObjects != null)
                {
                    foreach (WeakReference item in this.targetObjects)
                    {
                        Contract.Assume(item != null);
                        if (item.IsAlive)
                        {
                            return true;
                        }
                    }
                    return false;
                }
                return this.targetObject.IsAlive;
            }
        }

        /// <summary>
        /// Updates the value of the localized object.
        /// </summary>
        internal void UpdateTargetValue()
        {
            object targetProperty = this.targetProperty;
            if (targetProperty == null)
            {
                return;
            }
            if (targetProperty is DependencyProperty)
            {
                if (this.targetObject != null)
                {
                    DependencyObject targetObject =
                        this.targetObject.Target as DependencyObject;
                    if (targetObject != null)
                    {
                        targetObject.SetValue(
                            (DependencyProperty)targetProperty,
                            this.getLocalizedValue());
                    }
                }
                else if (this.targetObjects != null)
                {
                    foreach (WeakReference item in this.targetObjects)
                    {
                        DependencyObject targetObject =
                            item.Target as DependencyObject;
                        if (targetObject != null)
                        {
                            targetObject.SetValue(
                                (DependencyProperty)targetProperty,
                                this.getLocalizedValue());
                        }
                    }
                }
            }
            else if (targetProperty is PropertyInfo)
            {
                object targetObject = this.targetObject.Target;
                if (targetObject != null)
                {
                    ((PropertyInfo)targetProperty)
                        .SetValue(targetObject, this.getLocalizedValue(), null);
                }
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the resource key.
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// Gets or sets the formatting string to use.
        /// </summary>
        public string Format { get; set; }

        #endregion

        #region Base Overrides

        /// <summary>
        /// Returns the object that corresponds to the specified resource key.
        /// </summary>
        /// <param name="serviceProvider">An object that can provide services for the markup extension.</param>
        /// <returns>The object that corresponds to the specified resource key.</returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException("serviceProvider");
            }
            Contract.EndContractBlock();
            IProvideValueTarget service = serviceProvider
                .GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (service != null)
            {
                if (service.TargetProperty is DependencyProperty)
                {
                    this.targetProperty = service.TargetProperty;
                    if (service.TargetObject is DependencyObject)
                    {
                        var targetObject = new WeakReference(service.TargetObject);
                        // Verify if the extension is used in a template
                        // and has been already registered
                        if (this.targetObjects != null)
                        {
                            this.targetObjects.Add(targetObject);
                        }
                        else
                        {
                            this.targetObject = targetObject;
                            LocalizationManager.AddLocalization(this);
                        }
                    }
                    else
                    {
                        // The extension is used in a template
                        this.targetObjects = new List<WeakReference>();
                        LocalizationManager.AddLocalization(this);
                        return this;
                    }
                }
                else if (service.TargetProperty is PropertyInfo)
                {
                    this.targetProperty = service.TargetProperty;
                    this.targetObject = new WeakReference(service.TargetObject);
                    LocalizationManager.AddLocalization(this);
                }
            }
            return this.getLocalizedValue();
        }

        #endregion
    }
}
